package com.yao2san.dedup.crawler;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.corpus.tag.Nature;
import com.hankcs.hanlp.seg.common.Term;
import lombok.Data;
import lombok.Getter;
import org.apache.commons.codec.digest.MurmurHash3;

import java.util.*;
import java.util.stream.Collectors;

@Data
public class Similarity {
    /**
     * 计算两个句子的相似度
     *
     * @param sentence1 句子1
     * @param sentence2 句子2
     * @return
     */
    public static double getSimilarity(String sentence1, String sentence2) {
        List<String> sent1Words = getSplitWords(sentence1);
        List<String> sent2Words = getSplitWords(sentence2);
        List<String> allWords = mergeList(sent1Words, sent2Words);
        int[] statistic1 = statistic(allWords, sent1Words);
        int[] statistic2 = statistic(allWords, sent2Words);
        double dividend = 0;
        double divisor1 = 0;
        double divisor2 = 0;
        for (int i = 0; i < statistic1.length; i++) {
            dividend += statistic1[i] * statistic2[i];
            divisor1 += Math.pow(statistic1[i], 2);
            divisor2 += Math.pow(statistic2[i], 2);
        }
        return dividend / (Math.sqrt(divisor1) * Math.sqrt(divisor2));
    }

    /**
     * 使用HanLP对句子进行分词
     *
     * @param sentence 待分词的句子
     * @return
     */
    private static List<String> getSplitWords(String sentence) {
        // 分词
        return HanLP.segment(sentence).stream().map(a -> a.word).filter(s -> !"`~!@#$^&*()=|{}':;',\\[\\].<>/?~！@#￥……&*（）——|{}【】‘；：”“'。，、？ ".contains(s)).collect(Collectors.toList());
    }
    private static List<String> splitWords(String sentence) {
        // 分词
        return HanLP.segment(sentence).stream().map(a -> a.word).collect(Collectors.toList());
    }
    // 合并分词结果，列出所有的词
    private static List<String> mergeList(List<String> list1, List<String> list2) {
        List<String> result = new ArrayList<>();
        result.addAll(list1);
        result.addAll(list2);
        return result.stream().distinct().collect(Collectors.toList());
    }

    private static int[] statistic(List<String> allWords, List<String> sentWords) {
        int[] result = new int[allWords.size()];
        for (int i = 0; i < allWords.size(); i++) {
            result[i] = Collections.frequency(sentWords, allWords.get(i));
        }
        return result;
    }

    @Getter
    static class WordTerm extends Term {
        int frequency;
        long hash;
        List<Integer> weightedHash;

        public WordTerm(String word, Nature nature) {
            super(word, nature);
        }

        @Override
        public String toString() {
            return JSONObject.toJSONString(this);
        }
    }

    public static int hanmingDistance(String s1, String s2) {
        String h1 = simhash(s1);
        String h2 = simhash(s2);
        String a = "";
        String b = "";
        int distance = 0;
        int len = h1.length();
        for (int i = 0; i < len; i++) {
            if (h1.charAt(i) != h2.charAt(i)) {
                distance++;
                a += "==" + String.valueOf(h1.charAt(i)) + "==";
                b += "==" + String.valueOf(h2.charAt(i)) + "==";
            } else {
                a += "" + String.valueOf(h1.charAt(i)) + "";
                b += "" + String.valueOf(h2.charAt(i)) + "";
            }
        }
        //  System.out.println(a);
        //  System.out.println(b);
        return distance;
    }

    private static String simhash(String s) {
        //分词
        List<Term> segment = segment(s);
        Map<String, WordTerm> wordMap = new HashMap<>();
        //计算词频
        for (Term term : segment) {
            WordTerm wordTerm = wordMap.get(term.word);
            if (wordTerm == null) {
                wordTerm = new WordTerm(term.word, term.nature);
                //词频
                wordTerm.frequency = 1;
                //hash
                wordTerm.hash = MurmurHash3.hash64(term.word.getBytes());
                wordMap.put(term.word, wordTerm);
            } else {
                wordTerm.frequency += 1;
            }
        }
        //加权hash
        for (Map.Entry<String, WordTerm> wordTermEntry : wordMap.entrySet()) {
            WordTerm wordTerm = wordTermEntry.getValue();
            int frequency = wordTerm.frequency;
            long hash = wordTerm.hash;
            String hashBinaryString = Long.toBinaryString(hash);
            String[] hashArray = hashBinaryString.split("");
            List<Integer> collect = Lists.newArrayList(hashArray).stream().map(x -> {
                if ("0".equals(x)) {
                    return -frequency;
                } else return frequency;
            }).collect(Collectors.toList());
            int len = 64 - collect.size();
            for (int i = 0; i < len; i++) {
                collect.add(i, -frequency);
            }
            wordTerm.weightedHash = collect;
        }
        //生成64位simhash
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 64; i++) {
            //合并
            int sum = 0;
            for (Map.Entry<String, WordTerm> wordTermEntry : wordMap.entrySet()) {
                WordTerm wordTerm = wordTermEntry.getValue();
                sum += wordTerm.weightedHash.get(i);
            }
            //降维
            if (sum > 0) {
                sb.append(1);
            } else {
                sb.append(0);
            }
        }
        return sb.toString();
    }

    private static List<Term> segment(String s) {
        //TODO 移除停用词
        return HanLP.segment(s).stream().filter(term -> !"`~!@#$^&*()=|{}':;',\\[\\].<>/?~！@#￥……&*（）——|{}【】‘；：”“'。，、？ ".contains(term.word)).collect(Collectors.toList());
    }

    public static double similarity(String s1, String s2) {
        int d = hanmingDistance(s1, s2);
        double res = (1 / (Math.sqrt(2 * Math.PI * 0.16))) * Math.pow(Math.E, (-Math.pow(0.01 * (d - 1), 2)) / (2 * 0.0459 * 0.0459));
        /*BigDecimal b = new BigDecimal(res);
        double f1 = b.setScale(4, BigDecimal.ROUND_HALF_UP).doubleValue();*/
        if (d == 0)
            return 1.0;
        if (d > 24)
            return 0.0;
        return res;
    }

    public static double jaccard(String s1, String s2) {
        List<String> words1 = splitWords(s1);
        List<String> words2 = splitWords(s2);

        List<String> temp = new ArrayList<>();
        temp.addAll(words1);
        temp.addAll(words2);
        List<String> union = temp.stream().distinct().collect(Collectors.toList());
        List<String> intersect = new ArrayList<>();

        List<String> a = words1.stream().
                map(x -> words2.contains(x) ? x : null).
                collect(Collectors.toList());

        List<String> b = words2.stream().
                map(x -> words1.contains(x) ? x : null).
                collect(Collectors.toList());

        intersect.addAll(a);
        intersect.addAll(b);
        intersect = intersect.stream().distinct().collect(Collectors.toList());
        intersect.removeAll(Collections.singleton(null));

        return 1.0 * intersect.size() / union.size();
    }

    public static void main(String[] args) {
        String s1 = "太喜欢短评里的一句话了，如果你带着偏见去看的话，那你永远都不会满意。\n" +
                " 本来影评是没有这段的，但看了一些影评后还是决定再写一下，为什么看了半集或者看了几分钟就来打一星，最可怕的是我看到一个短评说打两星以上都是傻逼，不想多说什么，跟没有教养的人没有讨论的价值。热评里有一篇文章写为什么要喜欢爱情公寓，而不去喜欢其他的喜剧，他列举了很多国内优秀喜剧，并且狠狠的讽刺谩骂了爱情公寓，我告诉你为什么。我出生于00年，一个标准的零零后，那么我从小到大在电视上看过什么喜剧呢?武林外传，爱情公寓，家有儿女，没有了。这就是我们这一代人接触过的喜剧，而我们为什么喜欢爱情公寓，因为我们没有别的喜剧可以喜欢，我同样喜欢武林外传，可爱情公寓的题材与设定更适合我们当时的审美与理解能力，所以爱情公寓才会有了如此多的粉丝，那些无限贬低的人，当时不也是一集集看完的吗\n" +
                "说到抄袭，没得洗，但如果你想发表你的看法应该去前几季，这部剧目前还没有能证明抄袭的证据。美剧和抄袭剧还是有区别，我接触美剧大概是初中，看行尸走肉，血族，看过几集生活大爆炸，也是抄袭的受害者之一，说实话，那时候的我并不喜欢这部剧，因为他太成人化了，有太多我看不懂的段子以及大量成人笑话，别说什么看爱情公寓的人都青春其实都是美剧，一个电视剧哪来什么青春不青春的，就图一乐呵，看着有意思，这把美剧在电视上放，也不一定有多火，但实际上，大部分人在那时候上接触不到美剧的。";
        String s2 = "太喜欢短评里的一句话了，如果你带着偏见去看，那你永远都不会满意。本来影评是没有这段的，但看了一些影评后还是决定再写一下，为什么看了半集或者看了几分钟就来打一星，最可怕的是我看到一个短评说打两星以上都是傻逼，不想多说什么，跟没有教养的人没有讨论的价值。热评里有一篇文章写为什么要喜欢爱情公寓，而不去喜欢其他的喜剧，他列举了很多国内优秀喜剧，并且狠狠的讽刺谩骂了爱情公寓，我告诉你为什么。我出生于00年，一个标准的零零后，那么我从小到大在电视上看过什么喜剧呢?武林外传，爱情公寓，家有儿女，没有了。这就是我们这一代人接触过的喜剧，而我们为什么喜欢爱情公寓，因为我们没有别的喜剧可以喜欢，我同样喜欢武林外传，可爱情公寓的题材与设定更适合我们当时的审美与理解能力，所以爱情公寓才会有了如此多的粉丝，那些无限贬低的人，当时不也是一集集看完的吗？说到抄袭，没得洗，但如果你想发表你的看法应该去前几季，这部剧目前还没有能证明抄袭的证据。美剧和抄袭剧还是有区别，说实话，那时候的我并不喜欢这部剧，因为他太成人化了，有太多我看不懂的段子以及大量成人笑话，别说什么看爱情公寓的人都青春其实都是美剧，一个电视剧哪来什么青春不青春的，就图一乐呵，这把美剧在电视上放，也不一定有多火，但实际上，大部分人在那时候上接触不到美剧的。\n";
        String s3 = "爱5可谓后劲十足，被前几集劝退的观众可能会错过很多，豆瓣逐渐升高的评分也证明了爱5得到真正看下去的观众的认可，不在只是搞笑的喜剧可以让人看到演员的演技，也更能体会导演要表达东西\n" +
                "\n" +
                "虽然感情戏部分要讲的道理并不复杂，无非是一群年轻人长大后面临的生活困境，信任危机，他们不再只是无忧无虑的年轻人，他们要考虑未来，自己的工作，家庭，梦想，越来越多的压力是每个人成熟的必经之路，爱情公寓现实了许多，你更能找到与自身的相同点，也更愿意去思考\n" +
                "\n" +
                "感谢一些网友对我影评的认可，能够在骂声一片的评论区收获赞同是我的荣幸，也有许多持反对意见的朋友提出了质疑与不满，也对我的思考提供了帮助，让我认识到自己观点的不当之处，也试着去理解大家的看法\n" +
                "\n" +
                "爱情公寓系列以及这些事情一直存在争议，也很难有个结果，我不希望大家随波逐流，而是应该有自己的思考与认识，无论你对爱情公寓爱不释手还是嗤之以鼻，都不应该被网络的谩骂左右了观点。写这篇影评就是希望大家能够客观冷静的对待这部剧，虽然争吵避免不了，但还是希望大家能够和平探讨，尽量不要用极端的形容或是偷换概念，也请大家尊重每一位观众，我也会积极与大家沟通交流 ";

        //System.out.println(simhash(s1));
        //System.out.println(simhash(s2));
        //System.out.println(segment(s1));
        //System.out.println(segment(s2));
        /*System.out.println(simhash(s1));
        System.out.println(simhash(s2));
        System.out.println(hanmingDistance(s1, s2));
        System.out.println(hanmingDistance(s1, s3));
        System.out.println(hanmingDistance(s2, s3));
        System.out.println(getSimilarity(s1, s2));
        System.out.println(similarity(s1, s2));*/
        /*String s4 = "今天天气真好，适合去逛街，也适合晒太阳";
        String s5 = "今天天气不错，适合去玩，也适合去晒太阳";
        String s6 = "小明不喜欢和小红玩，因为小明不喜欢太阳";

        List<String> lists1 = Similarity.getSplitWords(s1);
        List<String> lists2 = Similarity.getSplitWords(s5);
        List<String> lists3 = Similarity.getSplitWords(s6);
        System.out.println(lists1);
        System.out.println(lists2);
        System.out.println(lists3);
        lists3.addAll(lists2);
        List<String> words = Similarity.mergeList(lists1, lists3);
        int[] fr = statistic(words, lists3);
        List<String> ff = new ArrayList<>();
        for (int i = 0; i < words.size(); i++) {
            String word = words.get(i);
            int f = fr[i];
            ff.add(word+":"+f);
        }
        System.out.println(words);
        System.out.println(ff);
        System.out.println(Arrays.toString(fr));*/

        /*System.out.println(getSimilarity(s1,s2));*/
        int step = 10;
        int max = 1000;
        StringBuilder sb1 = new StringBuilder();
        StringBuilder sb2 = new StringBuilder();
        for (int i = 0; i < 100; i++) {
            sb1.append(s1);
            sb2.append(s2);
        }
        //sb1 = new StringBuilder(sb1.subSequence(0, 100001));
        //sb2 = new StringBuilder(sb2.subSequence(0, 100001));
        String aa = sb1.toString();
        String bb = sb2.toString();
        long time = 0;
        for (int i = 0; i < 10; i++) {
            long start = System.currentTimeMillis();
            jaccard(aa, bb);
            time += System.currentTimeMillis() - start;
        }
        for (int i = 0; i < 10; i++) {
            long start = System.currentTimeMillis();
            getSimilarity(aa, bb);
            time += System.currentTimeMillis() - start;
        }
       /* for (int i = 0; i < 10; i++) {
            long start = System.currentTimeMillis();
            similarityEd(aa, bb);
            time += System.currentTimeMillis() - start;
        }*/
        //List<Double> res1= new ArrayList<>();
        //List<Double> res2= new ArrayList<>();
        List<Double> res3= new ArrayList<>();
        // System.out.println("总字符数:"+(aa.length()+bb.length())+",耗时:"+(time/10.0));
        for (int n = 1; n < max; n += step) {
           StringBuilder sbb1 = new StringBuilder(sb1.subSequence(0, n));
            StringBuilder sbb2 = new StringBuilder(sb2.subSequence(0, n));
            StringBuilder sbb3 = new StringBuilder(sb2.subSequence(0, n));
            aa = sbb1.toString();
            bb = sbb2.toString();
            time = 0;
           /* for (int i = 0; i < 10; i++) {
                long start = System.currentTimeMillis();
                similarity(aa, bb);
                time += System.currentTimeMillis() - start;
            }
           // System.out.println("总字符数:" + (aa.length() + bb.length()) + ",simhash耗时:" + (time / 10.0));
            res1.add(time / 10.0);
            time = 0;
            for (int i = 0; i < 10; i++) {
                long start = System.currentTimeMillis();
                getSimilarity(aa, bb);
                time += System.currentTimeMillis() - start;
            }
            //System.out.println("总字符数:" + (aa.length() + bb.length()) + ",余弦相似度耗时:" + (time / 10.0));
            res2.add(time / 10.0);*/

            time = 0;
            for (int i = 0; i < 10; i++) {
                long start = System.currentTimeMillis();
                jaccard(aa, bb);
                time += System.currentTimeMillis() - start;
            }
            //System.out.println("总字符数:" + (aa.length() + bb.length()) + ",最小编辑距离:" + (time / 10.0));
            res3.add(time / 10.0);
        }
        //System.out.println(res1);
       // System.out.println(res2);
        System.out.println(res3);
        /*System.out.println(getSimilarity(s1,s3));
        System.out.println(getSimilarity(s2,s3));*/

        System.out.println(jaccard(s1,s3));
    }


    static int d = 0;

    private static int dis(String s1, String s2, int i, int j) {
        if (i == 0 || j == 0) {
            return Math.max(i, j);
        }
        int op1 = dis(s1, s2, i - 1, j) + 1;
        int op2 = dis(s1, s2, i, j - 1) + 1;
        int op3 = dis(s1, s2, i - 1, j - 1);
        if (s1.charAt(i - 1) != s2.charAt(j - 1)) {
            op3 += 1;
        }
        return Math.min(Math.min(op1, op2), op3);
    }

    private static int dis(String s1, String s2) {
        int[][] temp = new int[s1.length()][s2.length()];
        for (int i = 0; i < s1.toCharArray().length; i++) {
            temp[i][0] = i;
        }
        for (int j = 0; j < s2.toCharArray().length; j++) {
            temp[0][j] = j;
        }
        for (int i = 1; i < s1.toCharArray().length; i++) {
            for (int j = 1; j < s2.toCharArray().length; j++) {
                if (s1.charAt(i) == s2.charAt(j)) {
                    temp[i][j] = temp[i - 1][j - 1];
                } else {
                    int op1 = temp[i - 1][j] + 1;
                    int op2 = temp[i - 1][j - 1] + 1;
                    int op3 = temp[i][j - 1] + 1;
                    temp[i][j] = Math.min(Math.min(op1, op2), op3);
                }
            }
        }
        return temp[s1.length() - 1][s2.length() - 1];
    }

    public static double similarityEd(String s1, String s2) {
        return (1 - dis(s1, s2) * 1.0 / Math.max(s1.length(), s2.length()));
    }
}

